#include "Managers/PlantsManager.h"
#include "Terrain/Terrain.h"
#include "Blocks/TallGrassBlock.h"
#include "Blocks/RyeBlock.h"
#include "Blocks/CottonBlock.h"
#include "Blocks/WoodBlock.h"
#include "Engine/EngineUtils.hpp"

#include "Subsystems/SubsystemWeather.h"
#include "Log.h"

namespace PocketSurvival
{
    PlantsManager::PlantsManager() : m_treeTrunksByType({9, 10, 11, 11, 255}), m_treeLeavesByType({12, 13, 14, 225, 256}) {}
    PlantsManager::~PlantsManager() {}

    void PlantsManager::loadGame()
    {
        Random random(30);

        m_treeBrushesByType.emplace_back();
        for (int32_t i = 0; i < 16; i++)
        {
            int32_t array[16]{5, 6, 7, 7, 8, 8, 9, 9, 9, 10, 10, 11, 12, 13, 14, 16};
            int32_t height4 = array[i];
            int32_t branchesCount = (int32_t)MathUtils::Lerp(10.0f, 20.0f, i / 16.0f);
            TerrainBrush &item = m_treeBrushesByType[0].emplace_back();
            createTreeBrush(
                item, random, getTreeTrunkValue(TreeType::Oak), getTreeLeavesValue(TreeType::Oak), height4, branchesCount,
                [&](int32_t y) -> float
                {
                    float num7 = 0.4f;
                    if (y < 0.2f * height4)
                    {
                        num7 = 0.0f;
                    }
                    else if (y >= 0.2f * height4 && y <= height4)
                    {
                        num7 *= 1.5f;
                    }
                    return num7;
                },
                [&](int32_t y) -> float
                {
                    if (y < height4 * 0.3f || y > height4 * 0.9f)
                    {
                        return 0.0f;
                    }
                    float num6 = (y < height4 * 0.7f) ? (0.5f * height4) : (0.35f * height4);
                    return random.getFloat(0.33f, 1.0f) * num6;
                });
        }

        m_treeBrushesByType.emplace_back();
        for (int32_t j = 0; j < 16; j++)
        {
            int32_t array2[]{4, 5, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11};
            int32_t height3 = array2[j];
            int32_t branchesCount2 = (int32_t)MathUtils::Lerp(0.0f, 20.0f, j / 16.0f);
            TerrainBrush &item = m_treeBrushesByType[1].emplace_back();
            createTreeBrush(
                item, random, getTreeTrunkValue(TreeType::Birch), getTreeLeavesValue(TreeType::Birch), height3, branchesCount2,
                [&](int32_t y) -> float
                {
                    float num5 = 0.66f;
                    if (y < height3 / 2 - 1)
                    {
                        num5 = 0.0f;
                    }
                    else if (y > height3 / 2 && y <= height3)
                    {
                        num5 *= 1.5f;
                    }
                    return num5;
                },
                [&](int32_t y) -> float
                {
                    return (y < height3 * 0.35f || y > height3 * 0.75f) ? 0.0f : random.getFloat(0.0f, 0.33f * height3);
                });
        }

        m_treeBrushesByType.emplace_back();
        for (int32_t k = 0; k < 16; k++)
        {
            int32_t array3[]{7, 8, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 16, 17};
            int32_t height2 = array3[k];
            int32_t branchesCount3 = height2 * 3;
            TerrainBrush &item = m_treeBrushesByType[2].emplace_back();
            createTreeBrush(
                item, random, getTreeTrunkValue(TreeType::Spruce), getTreeLeavesValue(TreeType::Spruce), height2, branchesCount3,
                [&](int32_t y) -> float
                {
                    float num4 = MathUtils::Lerp(1.4f, 0.3f, y / (float)height2);
                    if (y < 3)
                    {
                        num4 = 0.0f;
                    }
                    if (y % 2 == 0)
                    {
                        num4 *= 0.3f;
                    }
                    return num4;
                },
                [&](int32_t y) -> float
                {
                    if (y < 3 || y > height2 * 0.8f)
                    {
                        return 0.0f;
                    }
                    return (y % 2 == 0) ? 0.0f : MathUtils::Lerp(0.3f * height2, 0.0f, MathUtils::Saturate(y / (float)height2));
                });
        }

        m_treeBrushesByType.emplace_back();
        for (int32_t l = 0; l < 16; l++)
        {
            int32_t array4[]{20, 21, 22, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30};
            int32_t height = array4[l];
            int32_t branchesCount4 = height * 3;
            float startHeight = (0.3f + l % 4 * 0.05f) * height;
            TerrainBrush &item = m_treeBrushesByType[3].emplace_back();
            createTreeBrush(
                item, random, getTreeTrunkValue(TreeType::TallSpruce), getTreeLeavesValue(TreeType::TallSpruce), height, branchesCount4,
                [&](int32_t y) -> float
                {
                    float num2 = MathUtils::Saturate(y / (float)height);
                    float num3 = MathUtils::Lerp(1.5f, 0.0f, MathUtils::Saturate((num2 - 0.6f) / 0.4f));
                    if (y < startHeight)
                    {
                        num3 = 0.0f;
                    }
                    if (y % 3 != 0 && y < height - 4)
                    {
                        num3 *= 0.2f;
                    }
                    return num3;
                },
                [&](int32_t y) -> float
                {
                    float num = MathUtils::Saturate(y / (float)height);
                    if (y % 3 != 0)
                    {
                        return 0.0f;
                    }
                    return (y < startHeight) ? ((!(y < startHeight - 4.0f)) ? (0.1f * height) : 0.0f) : MathUtils::Lerp(0.18f * height, 0.0f, MathUtils::Saturate((num - 0.6f) / 0.4f));
                });
        }

        m_treeBrushesByType.emplace_back();
        for (int32_t m = 0; m < 16; m++)
        {
            TerrainBrush &item = m_treeBrushesByType[4].emplace_back();
            createMimosaBrush(item, random, MathUtils::Lerp(6.0f, 9.0f, m / 15.0f));
        }
    }

    void PlantsManager::createTreeBrush(
        TerrainBrush &terrainBrush,
        Random &random,
        int32_t woodIndex,
        int32_t leavesIndex,
        int32_t height,
        int32_t branchesCount,
        std::function<float(int32_t)> leavesProbabilityByHeight,
        std::function<float(int32_t)> branchesLengthByHeight)
    {
        TBBrush woodBrush(woodIndex);
        TBBrush leavesBrush(leavesIndex);
        TBBrush airBrush(0);
        TBCounter leavesCounter(leavesIndex);

        TBCounter funcCounter([woodIndex](int32_t v) -> int32_t
                              { return (Terrain::ExtractContents(v) == woodIndex && v != -1 ? 1 : 0); });

        terrainBrush.addRay(0, -1, 0, 0, height, 0, 1, 1, 1, &woodBrush);
        for (int32_t i = 0; i < branchesCount; i++)
        {
            int32_t x = 0;
            int32_t num = random.getInt(0, height);
            int32_t z = 0;
            float s = branchesLengthByHeight(num);
            Vector3 vector = Vector3::Normalize(Vector3(random.getFloat(-1.0f, 1.0f), random.getFloat(0.0f, 0.33f), random.getFloat(-1.0f, 1.0f))) * s;
            int32_t x2 = (int32_t)MathUtils::Round(vector.posX);
            int32_t y = num + (int32_t)MathUtils::Round(vector.posY);
            int32_t z2 = (int32_t)MathUtils::Round(vector.posZ);
            int32_t cutFace = 0;
            if (MathUtils::Abs(vector.posX) == MathUtils::Max(MathUtils::Abs(vector.posX), MathUtils::Abs(vector.posY), MathUtils::Abs(vector.posZ)))
            {
                cutFace = 1;
            }
            else if (MathUtils::Abs(vector.posY) == MathUtils::Max(MathUtils::Abs(vector.posX), MathUtils::Abs(vector.posY), MathUtils::Abs(vector.posZ)))
            {
                cutFace = 4;
            }
            TBBrush funcBrush([woodIndex, cutFace](int32_t v) -> int32_t
                              { return (v != -1 ? -1 : Terrain::MakeBlockValue(woodIndex, 0, WoodBlock::SetCutFace(0, cutFace))); });
            terrainBrush.addRay(x, num, z, x2, y, z2, 1, 1, 1, &funcBrush);
        }
        for (int32_t j = 0; j < 3; j++)
        {
            Point3 min, max;
            terrainBrush.calculateBounds(min, max);
            for (int32_t k = min.posX - 1; k <= max.posX + 1; k++)
            {
                for (int32_t l = min.posZ - 1; l <= max.posZ + 1; l++)
                {
                    for (int32_t m = 1; m <= max.posY + 1; m++)
                    {
                        float num2 = leavesProbabilityByHeight(m);
                        if (random.getFloat(0.0f, 1.0f) < num2 &&
                            terrainBrush.getValue(k, m, l) == -1 &&
                            (terrainBrush.countNonDiagonalNeighbors(k, m, l, &leavesCounter) != 0 ||
                             terrainBrush.countNonDiagonalNeighbors(k, m, l, &funcCounter) != 0))
                        {
                            terrainBrush.addCell(k, m, l, &airBrush);
                        }
                    }
                }
            }
            terrainBrush.replace(0, leavesIndex);
        }
        terrainBrush.addCell(0, height, 0, &leavesBrush);
        terrainBrush.compile();
    }

    void PlantsManager::createMimosaBrush(TerrainBrush &terrainBrush, Random &random, float size)
    {
        int32_t value = m_treeTrunksByType[4];
        int32_t value2 = m_treeLeavesByType[4];

        TBBrush trunksBrush(value);
        TBBrush leavesBrush(value2);

        terrainBrush.addRay(0, -1, 0, 0, 0, 0, 1, 1, 1, &trunksBrush);
        std::vector<Point3> list;

        float num = random.getFloat(0.0f, (float)MathUtils::GetPI() * 2.0f);
        for (int32_t i = 0; i < 3; i++)
        {
            float radians = num + i * MathUtils::DegToRad(120.0f);
            Matrix matrix;
            Matrix::CreateRotationY(radians, matrix);
            Vector3 v = EngineUtils::Transform(Vector3::Normalize(Vector3(1.0f, random.getFloat(1.0f, 1.5f), 0.0f)), matrix);
            int32_t num2 = random.getInt((int32_t)(0.7f * size), (int32_t)size);
            Point3 p(0);
            Point3 item = Vector3::Round(Vector3(p) + v * num2).toPoint3();
            terrainBrush.addRay(p.posX, p.posY, p.posZ, item.posX, item.posY, item.posZ, 1, 1, 1, &trunksBrush);
            list.push_back(item);
        }
        for (const Point3 &item2 : list)
        {
            float num3 = random.getFloat(0.3f * size, 0.45f * size);
            int32_t num4 = MathUtils::Ceil<int32_t>(num3);
            for (int32_t j = item2.posX - num4; j <= item2.posX + num4; j++)
            {
                for (int32_t k = item2.posY - num4; k <= item2.posY + num4; k++)
                {
                    for (int32_t l = item2.posZ - num4; l <= item2.posZ + num4; l++)
                    {
                        int32_t num5 = MathUtils::Abs<int32_t>(j - item2.posX) + MathUtils::Abs<int32_t>(k - item2.posY) + MathUtils::Abs<int32_t>(l - item2.posZ);
                        float num6 = ((Vector3(j, k, l) - Vector3(item2)) * Vector3(1.0f, 1.7f, 1.0f)).length();
                        if (num6 <= num3 && (num3 - num6 > 1.0f || num5 <= 2 || random.getBool(0.7f)) && terrainBrush.getValue(j, k, l) == -1)
                        {
                            terrainBrush.addCell(j, k, l, &leavesBrush);
                        }
                    }
                }
            }
        }
        terrainBrush.compile();
    }

    int32_t PlantsManager::generateRandomPlantValue(Random &random, int32_t groundValue, int32_t temperature, int32_t humidity, int32_t y)
    {
        switch (Terrain::ExtractContents(groundValue))
        {
        case 2:
        case 8:
            if (humidity >= 6)
            {
                if (false == (random.getFloat(0.0f, 1.0f) < humidity / 60.0f))
                {
                    break;
                }
                int32_t result = Terrain::MakeBlockValue(19, 0, TallGrassBlock::SetIsSmall(0, false));
                if (!SubsystemWeather::IsPlaceFrozen(temperature, y))
                {
                    float num = random.getFloat(0.0f, 1.0f);
                    if (num < 0.04f)
                    {
                        result = Terrain::MakeBlockValue(20);
                    }
                    else if (num < 0.07f)
                    {
                        result = Terrain::MakeBlockValue(24);
                    }
                    else if (num < 0.09f)
                    {
                        result = Terrain::MakeBlockValue(25);
                    }
                    else if (num < 0.17f)
                    {
                        result = Terrain::MakeBlockValue(174, 0, RyeBlock::SetIsWild(RyeBlock::SetSize(0, 7), true));
                    }
                    else if (num < 0.19f)
                    {
                        result = Terrain::MakeBlockValue(204, 0, CottonBlock::SetIsWild(CottonBlock::SetSize(0, 2), true));
                    }
                }
                return result;
            }
            if (random.getFloat(0.0f, 1.0f) < 0.025f)
            {
                if (random.getFloat(0.0f, 1.0f) < 0.2f)
                {
                    return Terrain::MakeBlockValue(99, 0, 0);
                }
                return Terrain::MakeBlockValue(28, 0, 0);
            }
            break;
        case 7:
            if (humidity < 8 && random.getFloat(0.0f, 1.0f) < 0.01f)
            {
                if (random.getFloat(0.0f, 1.0f) < 0.05f)
                {
                    return Terrain::MakeBlockValue(99, 0, 0);
                }
                return Terrain::MakeBlockValue(28, 0, 0);
            }
            break;
        }
        return 0;
    }

    TreeType PlantsManager::generateRandomTreeType(Random &random, int32_t temperature, int32_t humidity, int32_t y, float densityMultiplier)
    {
        TreeType result = TreeType::None;
        float num = random.getFloat() * calculateTreeProbability(TreeType::Oak, temperature, humidity, y);
        float num2 = random.getFloat() * calculateTreeProbability(TreeType::Birch, temperature, humidity, y);
        float num3 = random.getFloat() * calculateTreeProbability(TreeType::Spruce, temperature, humidity, y);
        float num4 = random.getFloat() * calculateTreeProbability(TreeType::TallSpruce, temperature, humidity, y);
        float num5 = random.getFloat() * calculateTreeProbability(TreeType::Mimosa, temperature, humidity, y);
        float num6 = MathUtils::Max(MathUtils::Max(num, num2, num3, num4), num5);
        if (num6 > 0.0f)
        {
            if (num6 == num)
            {
                result = TreeType::Oak;
            }
            if (num6 == num2)
            {
                result = TreeType::Birch;
            }
            if (num6 == num3)
            {
                result = TreeType::Spruce;
            }
            if (num6 == num4)
            {
                result = TreeType::TallSpruce;
            }
            if (num6 == num5)
            {
                result = TreeType::Mimosa;
            }
        }
        if (result != TreeType::None && random.getBool(densityMultiplier * calculateTreeDensity(result, temperature, humidity, y)))
        {
            return result;
        }
        return TreeType::None;
    }

    float PlantsManager::calculateTreeDensity(TreeType treeType, int32_t temperature, int32_t humidity, int32_t y)
    {
        switch (treeType)
        {
        case TreeType::Oak:
            return rangeProbability(humidity, 4.0f, 15.0f, 15.0f, 15.0f);
        case TreeType::Birch:
            return rangeProbability(humidity, 4.0f, 15.0f, 15.0f, 15.0f);
        case TreeType::Spruce:
            return rangeProbability(humidity, 4.0f, 15.0f, 15.0f, 15.0f);
        case TreeType::TallSpruce:
            return rangeProbability(humidity, 4.0f, 15.0f, 15.0f, 15.0f);
        case TreeType::Mimosa:
            return 0.03f;
        default:
            return 0.0f;
        }
    }

    float PlantsManager::calculateTreeProbability(TreeType treeType, int32_t temperature, int32_t humidity, int32_t y)
    {
        switch (treeType)
        {
        case TreeType::Oak:
            return rangeProbability(temperature, 4.0f, 10.0f, 15.0f, 15.0f) * rangeProbability(humidity, 6.0f, 8.0f, 15.0f, 15.0f) * rangeProbability(y, 0.0f, 0.0f, 82.0f, 87.0f);
        case TreeType::Birch:
            return rangeProbability(temperature, 5.0f, 9.0f, 9.0f, 14.0f) * rangeProbability(humidity, 3.0f, 15.0f, 15.0f, 15.0f) * rangeProbability(y, 0.0f, 0.0f, 82.0f, 87.0f);
        case TreeType::Spruce:
            return rangeProbability(temperature, 0.0f, 0.0f, 6.0f, 10.0f) * rangeProbability(humidity, 3.0f, 10.0f, 11.0f, 12.0f);
        case TreeType::TallSpruce:
            return 0.25f * rangeProbability(temperature, 0.0f, 0.0f, 6.0f, 10.0f) * rangeProbability(humidity, 9.0f, 11.0f, 15.0f, 15.0f) * rangeProbability(y, 0.0f, 0.0f, 95.0f, 100.0f);
        case TreeType::Mimosa:
            return rangeProbability(temperature, 2.0f, 4.0f, 12.0f, 14.0f) * rangeProbability(humidity, 0.0f, 0.0f, 4.0f, 6.0f);
        default:
            return 0.0f;
        }
    }

    float PlantsManager::rangeProbability(float v, float a, float b, float c, float d)
    {
        if (v < a)
        {
            return 0.0f;
        }
        if (v < b)
        {
            return (v - a) / (b - a);
        }
        if (v <= c)
        {
            return 1.0f;
        }
        if (v <= d)
        {
            return 1.0f - (v - c) / (d - c);
        }
        return 0.0f;
    }

} // namespace PocketSurvival
